Merge pull request #215 from guzba/master
underline, strikethrough, insertion sort for hits is faster, context.globalAlpha
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import pixie, wNim/[wApp, wFrame, wPaintDC, wImage]
|
||||
import pixie, wNim/wApp, wNim/wFrame, wNim/wImage, wNim/wPaintDC
|
||||
|
||||
let
|
||||
w = 256
|
||||
|
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 915 B After Width: | Height: | Size: 918 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
)
|
BIN
tests/fonts/diffs/spans6.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
tests/fonts/diffs/strikethrough1.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
tests/fonts/diffs/strikethrough2.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
tests/fonts/diffs/strikethrough3.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
tests/fonts/diffs/underline1.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
tests/fonts/diffs/underline2.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
tests/fonts/diffs/underline3.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
tests/fonts/masters/spans6.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
tests/fonts/masters/strikethrough1.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tests/fonts/masters/strikethrough2.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
tests/fonts/masters/strikethrough3.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
tests/fonts/masters/underline1.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
tests/fonts/masters/underline2.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
tests/fonts/masters/underline3.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
tests/fonts/rendered/spans6.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
tests/fonts/rendered/strikethrough1.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
tests/fonts/rendered/strikethrough2.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
tests/fonts/rendered/strikethrough3.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
tests/fonts/rendered/underline1.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
tests/fonts/rendered/underline2.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
tests/fonts/rendered/underline3.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
|
@ -1,4 +1,4 @@
|
|||
import pixie, random, os
|
||||
import os, pixie, random
|
||||
|
||||
when not defined(pixieLeakCheck):
|
||||
quit("Requires -d:pixieLeakCheck")
|
||||
|
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
tests/images/context/globalAlpha_1.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 982 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 896 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 357 KiB After Width: | Height: | Size: 357 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
Before Width: | Height: | Size: 643 KiB After Width: | Height: | Size: 644 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 339 KiB |
Before Width: | Height: | Size: 3.3 MiB After Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 610 KiB After Width: | Height: | Size: 610 KiB |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 519 KiB After Width: | Height: | Size: 519 KiB |
Before Width: | Height: | Size: 3.9 MiB After Width: | Height: | Size: 3.9 MiB |
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|