diff --git a/examples/blur.png b/examples/blur.png index 04340b4..a56cd1b 100644 Binary files a/examples/blur.png and b/examples/blur.png differ diff --git a/examples/gradient.png b/examples/gradient.png index ae1ba1b..9e76abb 100644 Binary files a/examples/gradient.png and b/examples/gradient.png differ diff --git a/examples/heart.png b/examples/heart.png index 7082326..968e975 100644 Binary files a/examples/heart.png and b/examples/heart.png differ diff --git a/examples/image_tiled.png b/examples/image_tiled.png index 897b8da..a892e15 100644 Binary files a/examples/image_tiled.png and b/examples/image_tiled.png differ diff --git a/examples/line.png b/examples/line.png index 0791cdd..7bcf2f6 100644 Binary files a/examples/line.png and b/examples/line.png differ diff --git a/examples/masking.png b/examples/masking.png index 19d6f33..c95e421 100644 Binary files a/examples/masking.png and b/examples/masking.png differ diff --git a/examples/realtime_win32.nim b/examples/realtime_win32.nim index d4c2d9c..1148b22 100644 --- a/examples/realtime_win32.nim +++ b/examples/realtime_win32.nim @@ -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) diff --git a/examples/realtime_wnim.nim b/examples/realtime_wnim.nim index acea7b2..e88bfaf 100644 --- a/examples/realtime_wnim.nim +++ b/examples/realtime_wnim.nim @@ -1,4 +1,4 @@ -import pixie, wNim/[wApp, wFrame, wPaintDC, wImage] +import pixie, wNim/wApp, wNim/wFrame, wNim/wImage, wNim/wPaintDC let w = 256 diff --git a/examples/rounded_rectangle.png b/examples/rounded_rectangle.png index a52a9a8..f8596ec 100644 Binary files a/examples/rounded_rectangle.png and b/examples/rounded_rectangle.png differ diff --git a/examples/shadow.png b/examples/shadow.png index d476390..4bcbae3 100644 Binary files a/examples/shadow.png and b/examples/shadow.png differ diff --git a/examples/square.png b/examples/square.png index e828ae2..1f0a431 100644 Binary files a/examples/square.png and b/examples/square.png differ diff --git a/examples/text.png b/examples/text.png index 7483842..e7b6f53 100644 Binary files a/examples/text.png and b/examples/text.png differ diff --git a/examples/text_spans.png b/examples/text_spans.png index 41bf361..6b0a04b 100644 Binary files a/examples/text_spans.png and b/examples/text_spans.png differ diff --git a/examples/tiger.png b/examples/tiger.png index 396004d..21f7ce0 100644 Binary files a/examples/tiger.png and b/examples/tiger.png differ diff --git a/src/pixie/context.nim b/src/pixie/context.nim index 1f46bab..85c16a7 100644 --- a/src/pixie/context.nim +++ b/src/pixie/context.nim @@ -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() diff --git a/src/pixie/fontformats/opentype.nim b/src/pixie/fontformats/opentype.nim index 477c181..e34cce6 100644 --- a/src/pixie/fontformats/opentype.nim +++ b/src/pixie/fontformats/opentype.nim @@ -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 diff --git a/src/pixie/fonts.nim b/src/pixie/fonts.nim index 764b0b0..73a257a 100644 --- a/src/pixie/fonts.nim +++ b/src/pixie/fonts.nim @@ -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, diff --git a/src/pixie/paths.nim b/src/pixie/paths.nim index 6ba6eb5..39b8aff 100644 --- a/src/pixie/paths.nim +++ b/src/pixie/paths.nim @@ -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, diff --git a/tests/benchmark_text.nim b/tests/benchmark_text.nim deleted file mode 100644 index 0adee60..0000000 --- a/tests/benchmark_text.nim +++ /dev/null @@ -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 - ) diff --git a/tests/fonts/diffs/spans6.png b/tests/fonts/diffs/spans6.png new file mode 100644 index 0000000..f8a226a Binary files /dev/null and b/tests/fonts/diffs/spans6.png differ diff --git a/tests/fonts/diffs/strikethrough1.png b/tests/fonts/diffs/strikethrough1.png new file mode 100644 index 0000000..d833e60 Binary files /dev/null and b/tests/fonts/diffs/strikethrough1.png differ diff --git a/tests/fonts/diffs/strikethrough2.png b/tests/fonts/diffs/strikethrough2.png new file mode 100644 index 0000000..4321b53 Binary files /dev/null and b/tests/fonts/diffs/strikethrough2.png differ diff --git a/tests/fonts/diffs/strikethrough3.png b/tests/fonts/diffs/strikethrough3.png new file mode 100644 index 0000000..2fbe162 Binary files /dev/null and b/tests/fonts/diffs/strikethrough3.png differ diff --git a/tests/fonts/diffs/underline1.png b/tests/fonts/diffs/underline1.png new file mode 100644 index 0000000..65b24e3 Binary files /dev/null and b/tests/fonts/diffs/underline1.png differ diff --git a/tests/fonts/diffs/underline2.png b/tests/fonts/diffs/underline2.png new file mode 100644 index 0000000..80e75aa Binary files /dev/null and b/tests/fonts/diffs/underline2.png differ diff --git a/tests/fonts/diffs/underline3.png b/tests/fonts/diffs/underline3.png new file mode 100644 index 0000000..c169d93 Binary files /dev/null and b/tests/fonts/diffs/underline3.png differ diff --git a/tests/fonts/image_stroke.png b/tests/fonts/image_stroke.png index 33b744b..3caaca2 100644 Binary files a/tests/fonts/image_stroke.png and b/tests/fonts/image_stroke.png differ diff --git a/tests/fonts/mask_stroke.png b/tests/fonts/mask_stroke.png index d95d4c9..01b15c2 100644 Binary files a/tests/fonts/mask_stroke.png and b/tests/fonts/mask_stroke.png differ diff --git a/tests/fonts/masters/spans6.png b/tests/fonts/masters/spans6.png new file mode 100644 index 0000000..877b3d5 Binary files /dev/null and b/tests/fonts/masters/spans6.png differ diff --git a/tests/fonts/masters/strikethrough1.png b/tests/fonts/masters/strikethrough1.png new file mode 100644 index 0000000..fa5da6b Binary files /dev/null and b/tests/fonts/masters/strikethrough1.png differ diff --git a/tests/fonts/masters/strikethrough2.png b/tests/fonts/masters/strikethrough2.png new file mode 100644 index 0000000..03d8ad0 Binary files /dev/null and b/tests/fonts/masters/strikethrough2.png differ diff --git a/tests/fonts/masters/strikethrough3.png b/tests/fonts/masters/strikethrough3.png new file mode 100644 index 0000000..a02808e Binary files /dev/null and b/tests/fonts/masters/strikethrough3.png differ diff --git a/tests/fonts/masters/underline1.png b/tests/fonts/masters/underline1.png new file mode 100644 index 0000000..9b05012 Binary files /dev/null and b/tests/fonts/masters/underline1.png differ diff --git a/tests/fonts/masters/underline2.png b/tests/fonts/masters/underline2.png new file mode 100644 index 0000000..fcbde16 Binary files /dev/null and b/tests/fonts/masters/underline2.png differ diff --git a/tests/fonts/masters/underline3.png b/tests/fonts/masters/underline3.png new file mode 100644 index 0000000..eb3e8b7 Binary files /dev/null and b/tests/fonts/masters/underline3.png differ diff --git a/tests/fonts/rendered/spans6.png b/tests/fonts/rendered/spans6.png new file mode 100644 index 0000000..3164950 Binary files /dev/null and b/tests/fonts/rendered/spans6.png differ diff --git a/tests/fonts/rendered/strikethrough1.png b/tests/fonts/rendered/strikethrough1.png new file mode 100644 index 0000000..30b8cff Binary files /dev/null and b/tests/fonts/rendered/strikethrough1.png differ diff --git a/tests/fonts/rendered/strikethrough2.png b/tests/fonts/rendered/strikethrough2.png new file mode 100644 index 0000000..88a8e0c Binary files /dev/null and b/tests/fonts/rendered/strikethrough2.png differ diff --git a/tests/fonts/rendered/strikethrough3.png b/tests/fonts/rendered/strikethrough3.png new file mode 100644 index 0000000..ff6d87e Binary files /dev/null and b/tests/fonts/rendered/strikethrough3.png differ diff --git a/tests/fonts/rendered/underline1.png b/tests/fonts/rendered/underline1.png new file mode 100644 index 0000000..bed08df Binary files /dev/null and b/tests/fonts/rendered/underline1.png differ diff --git a/tests/fonts/rendered/underline2.png b/tests/fonts/rendered/underline2.png new file mode 100644 index 0000000..11493d2 Binary files /dev/null and b/tests/fonts/rendered/underline2.png differ diff --git a/tests/fonts/rendered/underline3.png b/tests/fonts/rendered/underline3.png new file mode 100644 index 0000000..f7dec25 Binary files /dev/null and b/tests/fonts/rendered/underline3.png differ diff --git a/tests/fuzz_leaks2.nim b/tests/fuzz_leaks2.nim index b9a0570..8458d4f 100644 --- a/tests/fuzz_leaks2.nim +++ b/tests/fuzz_leaks2.nim @@ -1,4 +1,4 @@ -import pixie, random, os +import os, pixie, random when not defined(pixieLeakCheck): quit("Requires -d:pixieLeakCheck") diff --git a/tests/fuzz_leaks3.nim b/tests/fuzz_leaks3.nim index 4834b42..7c4949c 100644 --- a/tests/fuzz_leaks3.nim +++ b/tests/fuzz_leaks3.nim @@ -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 diff --git a/tests/images/context/bezierCurveTo_1.png b/tests/images/context/bezierCurveTo_1.png index 83bb3bf..a007d41 100644 Binary files a/tests/images/context/bezierCurveTo_1.png and b/tests/images/context/bezierCurveTo_1.png differ diff --git a/tests/images/context/bezierCurveTo_2.png b/tests/images/context/bezierCurveTo_2.png index e7a04f5..bfb877e 100644 Binary files a/tests/images/context/bezierCurveTo_2.png and b/tests/images/context/bezierCurveTo_2.png differ diff --git a/tests/images/context/closePath_1.png b/tests/images/context/closePath_1.png index c91d8e6..bd5fc25 100644 Binary files a/tests/images/context/closePath_1.png and b/tests/images/context/closePath_1.png differ diff --git a/tests/images/context/ellipse_1.png b/tests/images/context/ellipse_1.png index 0f8f5fe..23d777a 100644 Binary files a/tests/images/context/ellipse_1.png and b/tests/images/context/ellipse_1.png differ diff --git a/tests/images/context/globalAlpha_1.png b/tests/images/context/globalAlpha_1.png new file mode 100644 index 0000000..519b4c8 Binary files /dev/null and b/tests/images/context/globalAlpha_1.png differ diff --git a/tests/images/context/quadracticCurveTo_1.png b/tests/images/context/quadracticCurveTo_1.png index 42ee8dc..a48003e 100644 Binary files a/tests/images/context/quadracticCurveTo_1.png and b/tests/images/context/quadracticCurveTo_1.png differ diff --git a/tests/images/context/strokeText_1.png b/tests/images/context/strokeText_1.png index fb22006..f408ac1 100644 Binary files a/tests/images/context/strokeText_1.png and b/tests/images/context/strokeText_1.png differ diff --git a/tests/images/masks/strokeEllipse.png b/tests/images/masks/strokeEllipse.png index af1c50c..4d3f3ee 100644 Binary files a/tests/images/masks/strokeEllipse.png and b/tests/images/masks/strokeEllipse.png differ diff --git a/tests/images/masks/strokePolygon.png b/tests/images/masks/strokePolygon.png index 4d88470..00e432a 100644 Binary files a/tests/images/masks/strokePolygon.png and b/tests/images/masks/strokePolygon.png differ diff --git a/tests/images/masks/strokeRoundedRect.png b/tests/images/masks/strokeRoundedRect.png index 7f7a31c..d567654 100644 Binary files a/tests/images/masks/strokeRoundedRect.png and b/tests/images/masks/strokeRoundedRect.png differ diff --git a/tests/images/paths/pathStroke3.png b/tests/images/paths/pathStroke3.png index 428b9fa..01c0f55 100644 Binary files a/tests/images/paths/pathStroke3.png and b/tests/images/paths/pathStroke3.png differ diff --git a/tests/images/paths/pixelScale.png b/tests/images/paths/pixelScale.png index 0abcddc..6b8606f 100644 Binary files a/tests/images/paths/pixelScale.png and b/tests/images/paths/pixelScale.png differ diff --git a/tests/images/strokeEllipse.png b/tests/images/strokeEllipse.png index 7c95036..50562a0 100644 Binary files a/tests/images/strokeEllipse.png and b/tests/images/strokeEllipse.png differ diff --git a/tests/images/strokePolygon.png b/tests/images/strokePolygon.png index e124ee3..d833617 100644 Binary files a/tests/images/strokePolygon.png and b/tests/images/strokePolygon.png differ diff --git a/tests/images/strokeRoundedRect.png b/tests/images/strokeRoundedRect.png index 0ade755..fdfa991 100644 Binary files a/tests/images/strokeRoundedRect.png and b/tests/images/strokeRoundedRect.png differ diff --git a/tests/images/svg/Ghostscript_Tiger.png b/tests/images/svg/Ghostscript_Tiger.png index bf8f8a2..3908147 100644 Binary files a/tests/images/svg/Ghostscript_Tiger.png and b/tests/images/svg/Ghostscript_Tiger.png differ diff --git a/tests/images/svg/circle01.png b/tests/images/svg/circle01.png index fac810b..14fb41d 100644 Binary files a/tests/images/svg/circle01.png and b/tests/images/svg/circle01.png differ diff --git a/tests/images/svg/ellipse01.png b/tests/images/svg/ellipse01.png index 23d454c..c06bfb8 100644 Binary files a/tests/images/svg/ellipse01.png and b/tests/images/svg/ellipse01.png differ diff --git a/tests/images/svg/emojitwo.png b/tests/images/svg/emojitwo.png index 05177dc..81a5666 100644 Binary files a/tests/images/svg/emojitwo.png and b/tests/images/svg/emojitwo.png differ diff --git a/tests/images/svg/flat-color-icons.png b/tests/images/svg/flat-color-icons.png index 2be16bc..5597525 100644 Binary files a/tests/images/svg/flat-color-icons.png and b/tests/images/svg/flat-color-icons.png differ diff --git a/tests/images/svg/ionicons.png b/tests/images/svg/ionicons.png index f53bbd0..2025b35 100644 Binary files a/tests/images/svg/ionicons.png and b/tests/images/svg/ionicons.png differ diff --git a/tests/images/svg/miterlimit.png b/tests/images/svg/miterlimit.png index d36784d..75da14b 100644 Binary files a/tests/images/svg/miterlimit.png and b/tests/images/svg/miterlimit.png differ diff --git a/tests/images/svg/noto-emoji.png b/tests/images/svg/noto-emoji.png index 9f93974..6c9492b 100644 Binary files a/tests/images/svg/noto-emoji.png and b/tests/images/svg/noto-emoji.png differ diff --git a/tests/images/svg/openmoji.png b/tests/images/svg/openmoji.png index 2038f0d..c7deb8e 100644 Binary files a/tests/images/svg/openmoji.png and b/tests/images/svg/openmoji.png differ diff --git a/tests/images/svg/polygon01.png b/tests/images/svg/polygon01.png index c7fe070..7a43fe1 100644 Binary files a/tests/images/svg/polygon01.png and b/tests/images/svg/polygon01.png differ diff --git a/tests/images/svg/quad01.png b/tests/images/svg/quad01.png index 9722cce..ec45624 100644 Binary files a/tests/images/svg/quad01.png and b/tests/images/svg/quad01.png differ diff --git a/tests/images/svg/rect02.png b/tests/images/svg/rect02.png index 90f9702..accf977 100644 Binary files a/tests/images/svg/rect02.png and b/tests/images/svg/rect02.png differ diff --git a/tests/images/svg/simple-icons.png b/tests/images/svg/simple-icons.png index de85d50..2233473 100644 Binary files a/tests/images/svg/simple-icons.png and b/tests/images/svg/simple-icons.png differ diff --git a/tests/images/svg/tabler-icons.png b/tests/images/svg/tabler-icons.png index c27a0dc..e041516 100644 Binary files a/tests/images/svg/tabler-icons.png and b/tests/images/svg/tabler-icons.png differ diff --git a/tests/images/svg/triangle01.png b/tests/images/svg/triangle01.png index 994bc97..b521dbf 100644 Binary files a/tests/images/svg/triangle01.png and b/tests/images/svg/triangle01.png differ diff --git a/tests/images/svg/twbs-icons.png b/tests/images/svg/twbs-icons.png index 5f5ac82..e9638de 100644 Binary files a/tests/images/svg/twbs-icons.png and b/tests/images/svg/twbs-icons.png differ diff --git a/tests/images/svg/twemoji.png b/tests/images/svg/twemoji.png index 441a234..bfd58d4 100644 Binary files a/tests/images/svg/twemoji.png and b/tests/images/svg/twemoji.png differ diff --git a/tests/test_context.nim b/tests/test_context.nim index 49c0437..be0db11 100644 --- a/tests/test_context.nim +++ b/tests/test_context.nim @@ -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") diff --git a/tests/test_fonts.nim b/tests/test_fonts.nim index cf69394..e50214f 100644 --- a/tests/test_fonts.nim +++ b/tests/test_fonts.nim @@ -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")