Merge pull request #215 from guzba/master

underline, strikethrough, insertion sort for hits is faster, context.globalAlpha
This commit is contained in:
treeform 2021-06-01 13:09:52 -07:00 committed by GitHub
commit 0dfc513351
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 401 additions and 121 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -108,16 +108,26 @@ proc main() =
# Figure out the right size of the window we want.
var rect: lean.RECT
rect.left = 0
rect.top = 0
rect.right = w
rect.left = 0
rect.top = 0
rect.right = w
rect.bottom = h
AdjustWindowRectEx(cast[LPRECT](rect.addr), WS_OVERLAPPEDWINDOW, 0, 0)
# Open the window.
hwnd = CreateWindow(appName, "Win32/Pixie", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top,
0, 0, hInstance, nil)
hwnd = CreateWindow(
appName,
"Win32/Pixie",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
rect.right - rect.left,
rect.bottom - rect.top,
0,
0,
hInstance,
nil
)
ShowWindow(hwnd, SW_SHOW)
UpdateWindow(hwnd)

View file

@ -1,4 +1,4 @@
import pixie, wNim/[wApp, wFrame, wPaintDC, wImage]
import pixie, wNim/wApp, wNim/wFrame, wNim/wImage, wNim/wPaintDC
let
w = 256

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 B

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -10,6 +10,7 @@ type
Context* = ref object
image*: Image
fillStyle*, strokeStyle*: Paint
globalAlpha*: float32
lineWidth*: float32
miterLimit*: float32
lineCap*: LineCap
@ -25,6 +26,7 @@ type
ContextState = object
fillStyle, strokeStyle: Paint
globalAlpha: float32
lineWidth: float32
miterLimit: float32
lineCap: LineCap
@ -44,6 +46,7 @@ proc newContext*(image: Image): Context =
result = Context()
result.image = image
result.mat = mat3()
result.globalAlpha = 1
result.lineWidth = 1
result.miterLimit = 10
result.fillStyle = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
@ -56,6 +59,7 @@ proc newContext*(width, height: int): Context {.inline.} =
proc state(ctx: Context): ContextState =
result.fillStyle = ctx.fillStyle
result.strokeStyle = ctx.strokeStyle
result.globalAlpha = ctx.globalAlpha
result.lineWidth = ctx.lineWidth
result.miterLimit = ctx.miterLimit
result.lineCap = ctx.lineCap
@ -94,6 +98,7 @@ proc restore*(ctx: Context) =
let state = ctx.stateStack.pop()
ctx.fillStyle = state.fillStyle
ctx.strokeStyle = state.strokeStyle
ctx.globalAlpha = state.globalAlpha
ctx.lineWidth = state.lineWidth
ctx.miterLimit = state.miterLimit
ctx.lineCap = state.lineCap
@ -113,9 +118,13 @@ proc restore*(ctx: Context) =
else: # Otherwise draw to the root image
ctx.image.draw(poppedLayer)
proc fill(
ctx: Context, image: Image, path: Path, windingRule: WindingRule
) {.inline.} =
proc fill(ctx: Context, image: Image, path: Path, windingRule: WindingRule) =
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.fillPath(
path,
ctx.fillStyle,
@ -123,7 +132,17 @@ proc fill(
windingRule
)
proc stroke(ctx: Context, image: Image, path: Path) {.inline.} =
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc stroke(ctx: Context, image: Image, path: Path) =
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.strokePath(
path,
ctx.strokeStyle,
@ -135,6 +154,10 @@ proc stroke(ctx: Context, image: Image, path: Path) {.inline.} =
ctx.lineDash
)
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
if ctx.font.typeface == nil:
raise newException(PixieError, "No font has been set on this Context")
@ -145,6 +168,12 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
ctx.font.paint = ctx.fillStyle
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.fillText(
ctx.font,
text,
@ -152,6 +181,10 @@ proc fillText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
hAlign = ctx.textAlign
)
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
if ctx.font.typeface == nil:
raise newException(PixieError, "No font has been set on this Context")
@ -162,6 +195,12 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
ctx.font.paint = ctx.strokeStyle
var image = image
if ctx.globalAlpha != 1:
ctx.saveLayer()
image = ctx.layer
image.strokeText(
ctx.font,
text,
@ -174,6 +213,10 @@ proc strokeText(ctx: Context, image: Image, text: string, at: Vec2) {.inline.} =
dashes = ctx.lineDash
)
if ctx.globalAlpha != 1:
ctx.layer.applyOpacity(ctx.globalAlpha)
ctx.restore()
proc beginPath*(ctx: Context) {.inline.} =
## Starts a new path by emptying the list of sub-paths.
ctx.path = Path()

View file

@ -304,11 +304,11 @@ type
lookupList: LookupList
PostTable = ref object
version: float32
italicAngle: float32
underlinePosition: int16
underlineThickness: int16
isFixedPitch: uint32
version*: float32
italicAngle*: float32
underlinePosition*: int16
underlineThickness*: int16
isFixedPitch*: uint32
OpenType* = ref object
buf*: string

View file

@ -18,6 +18,8 @@ type
lineHeight*: float32 ## The line height in pixels or AutoLineHeight for the font's default line height.
paint*: Paint
textCase*: TextCase
underline*: bool ## Apply an underline.
strikethrough*: bool ## Apply a strikethrough.
noKerningAdjustments*: bool ## Optionally disable kerning pair adjustments
Span* = ref object
@ -25,10 +27,11 @@ type
font*: Font
Arrangement* = ref object
spans*: seq[(int, int)] ## The (start, stop) of the spans in the text.
fonts*: seq[Font] ## The font for each span.
runes*: seq[Rune] ## The runes of the text.
positions*: seq[Vec2] ## The positions of the glyphs for each rune.
lines*: seq[(int, int)] ## The (start, stop) of the lines of text.
spans*: seq[(int, int)] ## The (start, stop) of the spans in the text.
fonts*: seq[Font] ## The font for each span.
runes*: seq[Rune] ## The runes of the text.
positions*: seq[Vec2] ## The positions of the glyphs for each rune.
selectionRects*: seq[Rect] ## The selection rects for each glyph.
HAlignMode* = enum
@ -72,6 +75,22 @@ proc lineHeight*(typeface: Typeface): float32 {.inline.} =
## The default line height in font units.
typeface.ascent - typeface.descent + typeface.lineGap
proc underlinePosition(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.post.underlinePosition.float32
proc underlineThickness(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.post.underlineThickness.float32
proc strikeoutPosition(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.os2.yStrikeoutPosition.float32
proc strikeoutThickness(typeface: Typeface): float32 {.inline.} =
if typeface.opentype != nil:
result = typeface.opentype.os2.yStrikeoutSize.float32
proc getGlyphPath*(typeface: Typeface, rune: Rune): Path {.inline.} =
## The glyph path for the rune.
if rune.uint32 > SP.uint32: # Empty paths for control runes (not tofu)
@ -177,7 +196,7 @@ proc typeset*(
result.positions.setLen(result.runes.len)
result.selectionRects.setLen(result.runes.len)
var lines = @[(0, 0)] # (start, stop)
result.lines = @[(0, 0)] # (start, stop)
block: # Arrange the glyphs horizontally first (handling line breaks)
proc advance(font: Font, runes: seq[Rune], i: int): float32 {.inline.} =
@ -200,10 +219,10 @@ proc typeset*(
at.x = 0
at.y += 1
prevCanWrap = 0
lines[^1][1] = runeIndex
result.lines[^1][1] = runeIndex
# Start a new line if we are not at the end
if runeIndex + 1 < result.runes.len:
lines.add((runeIndex + 1, 0))
result.lines.add((runeIndex + 1, 0))
else:
let advance = advance(font, result.runes, runeIndex)
if wrap and rune != SP and bounds.x > 0 and at.x + advance > bounds.x:
@ -221,8 +240,8 @@ proc typeset*(
at.x += advance(font, result.runes, i)
dec lineStart
lines[^1][1] = lineStart - 1
lines.add((lineStart, 0))
result.lines[^1][1] = lineStart - 1
result.lines.add((lineStart, 0))
if rune.canWrap():
prevCanWrap = runeIndex
@ -231,12 +250,12 @@ proc typeset*(
result.selectionRects[runeIndex] = rect(at.x, at.y, advance, 0)
at.x += advance
lines[^1][1] = result.runes.len - 1
result.lines[^1][1] = result.runes.len - 1
if hAlign != haLeft:
# Since horizontal alignment adjustments are different for each line,
# find the start and stop of each line of text.
for (start, stop) in lines:
for (start, stop) in result.lines:
var furthestX: float32
for i in countdown(stop, start):
if result.runes[i] != SP and result.runes[i] != LF:
@ -289,11 +308,11 @@ proc typeset*(
) / 2
maxInitialY = max(maxInitialY, round(fontUnitInitialY * font.scale))
for runeIndex in start .. stop:
if runeIndex == lines[0][1]:
if runeIndex == result.lines[0][1]:
break outer
maxInitialY
var lineHeights = newSeq[float32](lines.len)
var lineHeights = newSeq[float32](result.lines.len)
block: # Compute each line's line height
var line: int
for spanIndex, (start, stop) in result.spans:
@ -306,11 +325,12 @@ proc typeset*(
font.defaultLineHeight
lineHeights[line] = max(lineHeights[line], fontLineHeight)
for runeIndex in start .. stop:
if line + 1 < lines.len and runeIndex == lines[line + 1][0]:
if line + 1 < result.lines.len and
runeIndex == result.lines[line + 1][0]:
inc line
lineHeights[line] = max(lineHeights[line], fontLineHeight)
# Handle when span and line endings coincide
if line + 1 < lines.len and stop == lines[line][1]:
if line + 1 < result.lines.len and stop == result.lines[line][1]:
inc line
block: # Vertically position the glyphs
@ -326,7 +346,8 @@ proc typeset*(
else:
font.defaultLineHeight
for runeIndex in start .. stop:
if line + 1 < lines.len and runeIndex == lines[line + 1][0]:
if line + 1 < result.lines.len and
runeIndex == result.lines[line + 1][0]:
inc line
baseline += lineHeights[line]
result.positions[runeIndex].y = baseline
@ -404,24 +425,96 @@ proc parseSvgFont*(buf: string): Font =
result.lineHeight = AutoLineHeight
result.paint = Paint(kind: pkSolid, color: rgbx(0, 0, 0, 255))
proc textUber(
target: Image | Mask,
arrangement: Arrangement,
transform: Vec2 | Mat3 = vec2(0, 0),
strokeWidth = 1.0,
lineCap = lcButt,
lineJoin = ljMiter,
miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[],
stroke: static[bool] = false
) =
var line: int
for spanIndex, (start, stop) in arrangement.spans:
let
font = arrangement.fonts[spanIndex]
underlineThickness = font.typeface.underlineThickness * font.scale
underlinePosition = font.typeface.underlinePosition * font.scale
strikeoutThickness = font.typeface.strikeoutThickness * font.scale
strikeoutPosition = font.typeface.strikeoutPosition * font.scale
for runeIndex in start .. stop:
let position = arrangement.positions[runeIndex]
var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex])
path.transform(
translate(position) *
scale(vec2(font.scale))
)
var applyDecoration = true
if runeIndex == arrangement.lines[line][1]:
inc line
if arrangement.runes[runeIndex] == SP:
# Do not apply decoration to the space at end of lines
applyDecoration = false
if applyDecoration:
if font.underline:
path.rect(
arrangement.selectionRects[runeIndex].x,
position.y - underlinePosition + underlineThickness / 2,
arrangement.selectionRects[runeIndex].w,
underlineThickness
)
if font.strikethrough:
path.rect(
arrangement.selectionRects[runeIndex].x,
position.y - strikeoutPosition,
arrangement.selectionRects[runeIndex].w,
strikeoutThickness
)
when stroke:
when type(target) is Image:
target.strokePath(
path,
font.paint,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else: # target is Mask
target.strokePath(
path,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else:
when type(target) is Image:
target.fillPath(path, font.paint, transform)
else: # target is Mask
target.fillPath(path, transform)
proc fillText*(
target: Image | Mask,
arrangement: Arrangement,
transform: Vec2 | Mat3 = vec2(0, 0)
) =
) {.inline.} =
## Fills the text arrangement.
for spanIndex, (start, stop) in arrangement.spans:
let font = arrangement.fonts[spanIndex]
for runeIndex in start .. stop:
var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex])
path.transform(
translate(arrangement.positions[runeIndex]) *
scale(vec2(font.scale))
)
when type(target) is Image:
target.fillPath(path, font.paint, transform)
else: # target is Mask
target.fillPath(path, transform)
textUber(
target,
arrangement,
transform
)
proc fillText*(
target: Image | Mask,
@ -448,37 +541,19 @@ proc strokeText*(
lineJoin = ljMiter,
miterLimit = defaultMiterLimit,
dashes: seq[float32] = @[]
) =
) {.inline.} =
## Strokes the text arrangement.
for spanIndex, (start, stop) in arrangement.spans:
let font = arrangement.fonts[spanIndex]
for runeIndex in start .. stop:
var path = font.typeface.getGlyphPath(arrangement.runes[runeIndex])
path.transform(
translate(arrangement.positions[runeIndex]) *
scale(vec2(font.scale))
)
when type(target) is Image:
target.strokePath(
path,
font.paint,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
else: # target is Mask
target.strokePath(
path,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes
)
textUber(
target,
arrangement,
transform,
strokeWidth,
lineCap,
lineJoin,
miterLimit,
dashes,
true
)
proc strokeText*(
target: Image | Mask,

View file

@ -807,7 +807,7 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
of Move:
if shape.len > 0:
result.add(shape)
shape.setLen(0)
shape = newSeq[Vec2]()
at.x = command.numbers[0]
at.y = command.numbers[1]
start = at
@ -881,7 +881,7 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
of RMove:
if shape.len > 0:
result.add(shape)
shape.setLen(0)
shape = newSeq[Vec2]()
at.x += command.numbers[0]
at.y += command.numbers[1]
start = at
@ -959,7 +959,7 @@ proc commandsToShapes*(path: Path, pixelScale: float32 = 1.0): seq[seq[Vec2]] =
at = start
if shape.len > 0:
result.add(shape)
shape.setLen(0)
shape = newSeq[Vec2]()
prevCommandKind = command.kind
@ -995,8 +995,8 @@ proc computePixelBounds(segments: seq[(Segment, int16)]): Rect =
for (segment, _) in segments:
xMin = min(xMin, min(segment.at.x, segment.to.x))
xMax = max(xMax, max(segment.at.x, segment.to.x))
yMin = min(yMin, min(segment.at.y, segment.to.y))
yMax = max(yMax, max(segment.at.y, segment.to.y))
yMin = min(yMin, segment.at.y)
yMax = max(yMax, segment.to.y)
xMin = floor(xMin)
xMax = ceil(xMax)
@ -1071,6 +1071,16 @@ proc quickSort(a: var seq[(float32, int16)], inl, inr: int) =
quickSort(a, inl, r)
quickSort(a, l, inr)
proc insertionSort(s: var seq[(float32, int16)], hi: int) {.inline.} =
for i in 1 .. hi:
var
j = i - 1
k = i
while j >= 0 and s[j][0] > s[k][0]:
swap(s[j + 1], s[j])
dec j
dec k
proc shouldFill(windingRule: WindingRule, count: int): bool {.inline.} =
## Should we fill based on the current winding rule and count?
case windingRule:
@ -1113,7 +1123,10 @@ template computeCoverages(
hits[numHits] = (min(at.x, size.x), winding)
inc numHits
quickSort(hits, 0, numHits - 1)
if hits.len > 32:
quickSort(hits, 0, numHits - 1)
else:
insertionSort(hits, numHits - 1)
var
prevAt: float32
@ -1166,7 +1179,7 @@ template computeCoverages(
when defined(pixieLeakCheck):
if prevAt != size.x and count != 0:
echo "Leak detected: ", count, " @ ", prevAt, " ", y
echo "Leak detected: ", count, " @ (", prevAt, ", ", y, ")"
proc fillShapes(
image: Image,
@ -1184,15 +1197,14 @@ proc fillShapes(
bounds = computePixelBounds(segments)
startX = max(0, bounds.x.int)
startY = max(0, bounds.y.int)
stopY = min(image.height, (bounds.y + bounds.h).int)
pathHeight = stopY - startY
partitions = partitionSegments(segments, startY, pathHeight)
pathHeight = min(image.height, (bounds.y + bounds.h).int)
partitions = partitionSegments(segments, startY, pathHeight - startY)
var
coverages = newSeq[uint8](image.width)
hits = newSeq[(float32, int16)](4)
for y in startY ..< stopY:
for y in startY ..< pathHeight:
computeCoverages(
coverages,
hits,

View file

@ -1,16 +0,0 @@
import benchy, pixie
const paragraph = "She had come to the conclusion that you could tell a lot about a person by their ears. The way they stuck out and the size of the earlobes could give you wonderful insights into the person. Of course, she couldn't scientifically prove any of this, but that didn't matter to her. Before anything else, she would size up the ears of the person she was talking to. She's asked the question so many times that she barely listened to the answers anymore. The answers were always the same. Well, not exactly the same, but the same in a general sense. A more accurate description was the answers never surprised her."
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 16
let image = newImage(500, 300)
timeIt "paragraph":
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
paragraph,
bounds = image.wh
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -1,4 +1,4 @@
import pixie, random, os
import os, pixie, random
when not defined(pixieLeakCheck):
quit("Requires -d:pixieLeakCheck")

View file

@ -1,4 +1,4 @@
import pixie, random, pixie/fileformats/svg
import pixie, pixie/fileformats/svg, random
when not defined(pixieLeakCheck):
quit("Requires -d:pixieLeakCheck")
@ -8,7 +8,7 @@ randomize()
let data = readFile("tests/images/svg/Ghostscript_Tiger.svg")
for i in 0 ..< 100_000:
var image = decodeSvg(data, rand(300 .. 1800), rand(30 .. 1800))
var image = decodeSvg(data, rand(300 .. 1800), rand(30 .. 1800))
# image.writeFile("tests/fuzz_leaks3.png")
# break

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 896 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 KiB

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 KiB

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 519 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

View file

@ -499,20 +499,20 @@ block:
var y = 15.float32
proc drawDashedLine(pattern: seq[float32]) =
ctx.beginPath();
ctx.setLineDash(pattern);
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
y += 20;
ctx.beginPath()
ctx.setLineDash(pattern)
ctx.moveTo(0, y)
ctx.lineTo(300, y)
ctx.stroke()
y += 20
drawDashedLine(@[]);
drawDashedLine(@[1.float32, 1]);
drawDashedLine(@[10.float32, 10]);
drawDashedLine(@[20.float32, 5]);
drawDashedLine(@[15.float32, 3, 3, 3]);
drawDashedLine(@[20.float32, 3, 3, 3, 3, 3, 3, 3]);
drawDashedLine(@[12.float32, 3, 3]);
drawDashedLine(@[])
drawDashedLine(@[1.float32, 1])
drawDashedLine(@[10.float32, 10])
drawDashedLine(@[20.float32, 5])
drawDashedLine(@[15.float32, 3, 3, 3])
drawDashedLine(@[20.float32, 3, 3, 3, 3, 3, 3, 3])
drawDashedLine(@[12.float32, 3, 3])
image.writeFile("tests/images/context/setLineDash_1.png")
@ -531,3 +531,20 @@ block:
ctx.fillRect(10, 10, 100, 100)
image.writeFile("tests/images/context/blendmode_1.png")
block:
let
image = newImage(300, 150)
ctx = newContext(image)
image.fill(rgba(255, 255, 255, 255))
ctx.globalAlpha = 0.5
ctx.fillStyle = "blue"
ctx.fillRect(10, 10, 100, 100)
ctx.fillStyle = "red"
ctx.fillRect(50, 50, 100, 100)
image.writeFile("tests/images/context/globalAlpha_1.png")

View file

@ -781,18 +781,18 @@ block:
var font1 = ubuntu
font1.size = 15
font1.paint.color = parseHtmlColor("#CACACA").rgba()
font1.paint = "#CACACA"
var font2 = ubuntu
font2.size = 84
var font3 = ubuntu
font3.size = 18
font3.paint.color = parseHtmlColor("#007FF4").rgba()
font3.paint = "#007FF4"
var font4 = ubuntu
font4.size = 20
font4.paint.color = parseHtmlColor("#4F4F4F").rgba()
font4.paint = "#4F4F4F"
let spans = @[
newSpan("verb [with object] ", font1),
@ -809,3 +809,142 @@ block:
image.fillText(arrangement, vec2(20, 20))
doDiff(image, "spans5")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.underline = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "underline1")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.underline = true
font.paint = rgba(0, 0, 0, 127)
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "underline2")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.underline = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.strokeText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "underline3")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.strikethrough = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "strikethrough1")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.strikethrough = true
font.paint = rgba(0, 0, 0, 127)
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.fillText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "strikethrough2")
block:
var font = readFont("tests/fonts/Roboto-Regular_1.ttf")
font.size = 24
font.strikethrough = true
let image = newImage(200, 100)
image.fill(rgba(255, 255, 255, 255))
image.strokeText(
font,
"Wrapping text to new line",
bounds = vec2(200, 0)
)
doDiff(image, "strikethrough3")
block:
let ubuntu = readFont("tests/fonts/Ubuntu-Regular_1.ttf")
var font1 = ubuntu
font1.size = 15
font1.paint = "#CACACA"
var font2 = ubuntu
font2.size = 84
var font3 = ubuntu
font3.size = 18
font3.paint = "#007FF4"
var font4 = ubuntu
font4.size = 20
font4.paint = "#4F4F4F"
var font5 = ubuntu
font5.size = 20
font5.paint = "#4F4F4F"
font5.underline = true
var font6 = ubuntu
font6.size = 20
font6.paint = "#4F4F4F"
font6.strikethrough = true
let spans = @[
newSpan("verb [with object] ", font1),
newSpan("strallow\n", font2),
newSpan("\nstral·low\n", font3),
newSpan("\n1. free (something) from ", font4),
newSpan("restrictive restrictions", font5),
newSpan(" ", font4),
newSpan("\"the regulations are intended to strallow changes in public policy\" ", font6)
]
let image = newImage(400, 400)
image.fill(rgba(255, 255, 255, 255))
let arrangement = typeset(spans, bounds = vec2(360, 360))
image.fillText(arrangement, vec2(20, 20))
doDiff(image, "spans6")