⛏️ index : haiku.git

/*
 * Copyright 2001-2018, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		DarkWyrm <bpmagic@columbus.rr.com>
 *		Adi Oanca <adioanca@mymail.ro>
 *		Stephan Aßmus <superstippi@gmx.de>
 *		Axel Dörfler, axeld@pinc-software.de
 *		Michael Pfeiffer <laplace@users.sourceforge.net>
 *		Julian Harnath <julian.harnath@rwth-aachen.de>
 *		Joseph Groover <looncraz@looncraz.net>
 */

//!	Data classes for working with BView states and draw parameters

#include "DrawState.h"

#include <new>
#include <stdio.h>

#include <Region.h>
#include <ShapePrivate.h>

#include "AlphaMask.h"
#include "LinkReceiver.h"
#include "LinkSender.h"
#include "ServerProtocolStructs.h"


using std::nothrow;


DrawState::DrawState()
	:
	fOrigin(0.0f, 0.0f),
	fCombinedOrigin(0.0f, 0.0f),
	fScale(1.0f),
	fCombinedScale(1.0f),
	fTransform(),
	fCombinedTransform(),
	fAlphaMask(NULL),

	fHighColor((rgb_color){ 0, 0, 0, 255 }),
	fLowColor((rgb_color){ 255, 255, 255, 255 }),
	fWhichHighColor(B_NO_COLOR),
	fWhichLowColor(B_NO_COLOR),
	fWhichHighColorTint(B_NO_TINT),
	fWhichLowColorTint(B_NO_TINT),
	fPattern(kSolidHigh),

	fDrawingMode(B_OP_COPY),
	fAlphaSrcMode(B_PIXEL_ALPHA),
	fAlphaFncMode(B_ALPHA_OVERLAY),
	fDrawingModeLocked(false),

	fPenLocation(0.0f, 0.0f),
	fPenSize(1.0f),

	fFontAliasing(false),
	fSubPixelPrecise(false),
	fLineCapMode(B_BUTT_CAP),
	fLineJoinMode(B_MITER_JOIN),
	fMiterLimit(B_DEFAULT_MITER_LIMIT),
	fFillRule(B_NONZERO)
{
	fUnscaledFontSize = fFont.Size();
}


DrawState::DrawState(const DrawState& other)
	:
	fOrigin(other.fOrigin),
	fCombinedOrigin(other.fCombinedOrigin),
	fScale(other.fScale),
	fCombinedScale(other.fCombinedScale),
	fTransform(other.fTransform),
	fCombinedTransform(other.fCombinedTransform),
	fClippingRegion(NULL),
	fAlphaMask(NULL),

	fHighColor(other.fHighColor),
	fLowColor(other.fLowColor),
	fWhichHighColor(other.fWhichHighColor),
	fWhichLowColor(other.fWhichLowColor),
	fWhichHighColorTint(other.fWhichHighColorTint),
	fWhichLowColorTint(other.fWhichLowColorTint),
	fPattern(other.fPattern),

	fDrawingMode(other.fDrawingMode),
	fAlphaSrcMode(other.fAlphaSrcMode),
	fAlphaFncMode(other.fAlphaFncMode),
	fDrawingModeLocked(other.fDrawingModeLocked),

	fPenLocation(other.fPenLocation),
	fPenSize(other.fPenSize),

	fFont(other.fFont),
	fFontAliasing(other.fFontAliasing),

	fSubPixelPrecise(other.fSubPixelPrecise),

	fLineCapMode(other.fLineCapMode),
	fLineJoinMode(other.fLineJoinMode),
	fMiterLimit(other.fMiterLimit),
	fFillRule(other.fFillRule),

	// Since fScale is reset to 1.0, the unscaled
	// font size is the current size of the font
	// (which is from->fUnscaledFontSize * from->fCombinedScale)
	fUnscaledFontSize(other.fUnscaledFontSize),
	fPreviousState(NULL)
{
}


DrawState::~DrawState()
{
}


DrawState*
DrawState::PushState()
{
	DrawState* next = new (nothrow) DrawState(*this);

	if (next != NULL) {
		// Prepare state as derived from this state
		next->fOrigin = BPoint(0.0, 0.0);
		next->fScale = 1.0;
		next->fTransform.Reset();
		next->fPreviousState.SetTo(this);
		next->SetAlphaMask(fAlphaMask);
	}

	return next;
}


DrawState*
DrawState::PopState()
{
	return fPreviousState.Detach();
}


uint16
DrawState::ReadFontFromLink(BPrivate::LinkReceiver& link,
	AppFontManager* fontManager)
{
	uint16 mask;
	link.Read<uint16>(&mask);

	if ((mask & B_FONT_FAMILY_AND_STYLE) != 0) {
		uint32 fontID;
		link.Read<uint32>(&fontID);
		fFont.SetFamilyAndStyle(fontID, fontManager);
	}

	if ((mask & B_FONT_SIZE) != 0) {
		float size;
		link.Read<float>(&size);
		fUnscaledFontSize = size;
		fFont.SetSize(fUnscaledFontSize * fCombinedScale);
	}

	if ((mask & B_FONT_SHEAR) != 0) {
		float shear;
		link.Read<float>(&shear);
		fFont.SetShear(shear);
	}

	if ((mask & B_FONT_ROTATION) != 0) {
		float rotation;
		link.Read<float>(&rotation);
		fFont.SetRotation(rotation);
	}

	if ((mask & B_FONT_FALSE_BOLD_WIDTH) != 0) {
		float falseBoldWidth;
		link.Read<float>(&falseBoldWidth);
		fFont.SetFalseBoldWidth(falseBoldWidth);
	}

	if ((mask & B_FONT_SPACING) != 0) {
		uint8 spacing;
		link.Read<uint8>(&spacing);
		fFont.SetSpacing(spacing);
	}

	if ((mask & B_FONT_ENCODING) != 0) {
		uint8 encoding;
		link.Read<uint8>(&encoding);
		fFont.SetEncoding(encoding);
	}

	if ((mask & B_FONT_FACE) != 0) {
		uint16 face;
		link.Read<uint16>(&face);
		fFont.SetFace(face);
	}

	if ((mask & B_FONT_FLAGS) != 0) {
		uint32 flags;
		link.Read<uint32>(&flags);
		fFont.SetFlags(flags);
	}

	return mask;
}


void
DrawState::ReadFromLink(BPrivate::LinkReceiver& link)
{
	ViewSetStateInfo info;

	link.Read<ViewSetStateInfo>(&info);

	// BAffineTransform is transmitted as a double array
	double transform[6];
	link.Read<double[6]>(&transform);
	if (fTransform.Unflatten(B_AFFINE_TRANSFORM_TYPE, transform,
		sizeof(transform)) != B_OK) {
		return;
	}

	fPenLocation = info.penLocation;
	fPenSize = info.penSize;
	fHighColor = info.highColor;
	fLowColor = info.lowColor;
	fWhichHighColor = info.whichHighColor;
	fWhichLowColor = info.whichLowColor;
	fWhichHighColorTint = info.whichHighColorTint;
	fWhichLowColorTint = info.whichLowColorTint;
	fPattern = info.pattern;
	fDrawingMode = info.drawingMode;
	fOrigin = info.origin;
	fScale = info.scale;
	fLineJoinMode = info.lineJoin;
	fLineCapMode = info.lineCap;
	fMiterLimit = info.miterLimit;
	fFillRule = info.fillRule;
	fAlphaSrcMode = info.alphaSourceMode;
	fAlphaFncMode = info.alphaFunctionMode;
	fFontAliasing = info.fontAntialiasing;

	if (fPreviousState.IsSet()) {
		fCombinedOrigin = fPreviousState->fCombinedOrigin + fOrigin;
		fCombinedScale = fPreviousState->fCombinedScale * fScale;
		fCombinedTransform = fPreviousState->fCombinedTransform * fTransform;
	} else {
		fCombinedOrigin = fOrigin;
		fCombinedScale = fScale;
		fCombinedTransform = fTransform;
	}


	// read clipping
	bool hasClippingRegion;
	link.Read<bool>(&hasClippingRegion);

	if (hasClippingRegion) {
		BRegion region;
		link.ReadRegion(&region);
		SetClippingRegion(&region);
	} else {
		// No user clipping used
		SetClippingRegion(NULL);
	}
}


void
DrawState::WriteToLink(BPrivate::LinkSender& link) const
{
	// Attach font state
	ViewGetStateInfo info;
	info.fontID = fFont.GetFamilyAndStyle();
	info.fontSize = fFont.Size();
	info.fontShear = fFont.Shear();
	info.fontRotation = fFont.Rotation();
	info.fontFalseBoldWidth = fFont.FalseBoldWidth();
	info.fontSpacing = fFont.Spacing();
	info.fontEncoding = fFont.Encoding();
	info.fontFace = fFont.Face();
	info.fontFlags = fFont.Flags();

	// Attach view state
	info.viewStateInfo.penLocation = fPenLocation;
	info.viewStateInfo.penSize = fPenSize;
	info.viewStateInfo.highColor = fHighColor;
	info.viewStateInfo.lowColor = fLowColor;
	info.viewStateInfo.whichHighColor = fWhichHighColor;
	info.viewStateInfo.whichLowColor = fWhichLowColor;
	info.viewStateInfo.whichHighColorTint = fWhichHighColorTint;
	info.viewStateInfo.whichLowColorTint = fWhichLowColorTint;
	info.viewStateInfo.pattern = (::pattern)fPattern.GetPattern();
	info.viewStateInfo.drawingMode = fDrawingMode;
	info.viewStateInfo.origin = fOrigin;
	info.viewStateInfo.scale = fScale;
	info.viewStateInfo.lineJoin = fLineJoinMode;
	info.viewStateInfo.lineCap = fLineCapMode;
	info.viewStateInfo.miterLimit = fMiterLimit;
	info.viewStateInfo.fillRule = fFillRule;
	info.viewStateInfo.alphaSourceMode = fAlphaSrcMode;
	info.viewStateInfo.alphaFunctionMode = fAlphaFncMode;
	info.viewStateInfo.fontAntialiasing = fFontAliasing;


	link.Attach<ViewGetStateInfo>(info);

	// BAffineTransform is transmitted as a double array
	double transform[6];
	if (fTransform.Flatten(transform, sizeof(transform)) != B_OK)
		return;
	link.Attach<double[6]>(transform);

	link.Attach<bool>(fClippingRegion.IsSet());
	if (fClippingRegion.IsSet())
		link.AttachRegion(*fClippingRegion.Get());
}


void
DrawState::SetOrigin(BPoint origin)
{
	fOrigin = origin;

	// NOTE: the origins of earlier states are never expected to
	// change, only the topmost state ever changes
	if (fPreviousState.IsSet()) {
		fCombinedOrigin.x = fPreviousState->fCombinedOrigin.x
			+ fOrigin.x * fPreviousState->fCombinedScale;
		fCombinedOrigin.y = fPreviousState->fCombinedOrigin.y
			+ fOrigin.y * fPreviousState->fCombinedScale;
	} else {
		fCombinedOrigin = fOrigin;
	}
}


void
DrawState::SetScale(float scale)
{
	if (fScale == scale)
		return;

	fScale = scale;

	// NOTE: the scales of earlier states are never expected to
	// change, only the topmost state ever changes
	if (fPreviousState.IsSet())
		fCombinedScale = fPreviousState->fCombinedScale * fScale;
	else
		fCombinedScale = fScale;

	// update font size
	// NOTE: This is what makes the call potentially expensive,
	// hence the introductory check
	fFont.SetSize(fUnscaledFontSize * fCombinedScale);
}


void
DrawState::SetTransform(BAffineTransform transform)
{
	if (fTransform == transform)
		return;

	fTransform = transform;

	// NOTE: the transforms of earlier states are never expected to
	// change, only the topmost state ever changes
	if (fPreviousState.IsSet())
		fCombinedTransform = fPreviousState->fCombinedTransform * fTransform;
	else
		fCombinedTransform = fTransform;
}


/* Can be used to temporarily disable all BAffineTransforms in the state
   stack, and later reenable them.
*/
void
DrawState::SetTransformEnabled(bool enabled)
{
	if (enabled) {
		BAffineTransform temp = fTransform;
		SetTransform(BAffineTransform());
		SetTransform(temp);
	}
	else
		fCombinedTransform = BAffineTransform();
}


DrawState*
DrawState::Squash() const
{
	DrawState* const squashedState = new(nothrow) DrawState(*this);
	return squashedState->PushState();
}


void
DrawState::SetClippingRegion(const BRegion* region)
{
	if (region) {
		if (fClippingRegion.IsSet())
			*fClippingRegion.Get() = *region;
		else
			fClippingRegion.SetTo(new(nothrow) BRegion(*region));
	} else {
		fClippingRegion.Unset();
	}
}


bool
DrawState::HasClipping() const
{
	if (fClippingRegion.IsSet())
		return true;
	if (fPreviousState.IsSet())
		return fPreviousState->HasClipping();
	return false;
}


bool
DrawState::HasAdditionalClipping() const
{
	return fClippingRegion.IsSet();
}


bool
DrawState::GetCombinedClippingRegion(BRegion* region) const
{
	if (fClippingRegion.IsSet()) {
		BRegion localTransformedClipping(*fClippingRegion.Get());
		SimpleTransform penTransform;
		Transform(penTransform);
		penTransform.Apply(&localTransformedClipping);
		if (fPreviousState.IsSet()
			&& fPreviousState->GetCombinedClippingRegion(region)) {
			localTransformedClipping.IntersectWith(region);
		}
		*region = localTransformedClipping;
		return true;
	} else {
		if (fPreviousState.IsSet())
			return fPreviousState->GetCombinedClippingRegion(region);
	}
	return false;
}


bool
DrawState::ClipToRect(BRect rect, bool inverse)
{
	if (!rect.IsValid()) {
		if (!inverse) {
			if (!fClippingRegion.IsSet())
				fClippingRegion.SetTo(new(nothrow) BRegion());
			else
				fClippingRegion->MakeEmpty();
		}
		return false;
	}

	if (!fCombinedTransform.IsIdentity()) {
		if (fCombinedTransform.IsDilation()) {
			BPoint points[2] = { rect.LeftTop(), rect.RightBottom() };
			fCombinedTransform.Apply(&points[0], 2);
			rect.Set(points[0].x, points[0].y, points[1].x, points[1].y);
		} else {
			uint32 ops[] = {
				OP_MOVETO | OP_LINETO | 3,
				OP_CLOSE
			};
			BPoint points[4] = {
				BPoint(rect.left,  rect.top),
				BPoint(rect.right, rect.top),
				BPoint(rect.right, rect.bottom),
				BPoint(rect.left,  rect.bottom)
			};
			shape_data rectShape;
			rectShape.opList = &ops[0];
			rectShape.opCount = 2;
			rectShape.opSize = sizeof(uint32) * 2;
			rectShape.ptList = &points[0];
			rectShape.ptCount = 4;
			rectShape.ptSize = sizeof(BPoint) * 4;

			ClipToShape(&rectShape, inverse);
			return true;
		}
	}

	if (inverse) {
		if (!fClippingRegion.IsSet()) {
			fClippingRegion.SetTo(new(nothrow) BRegion(BRect(
				-(1 << 16), -(1 << 16), (1 << 16), (1 << 16))));
				// TODO: we should have a definition for a rect (or region)
				// with "infinite" area. For now, this region size should do...
		}
		fClippingRegion->Exclude(rect);
	} else {
		if (!fClippingRegion.IsSet())
			fClippingRegion.SetTo(new(nothrow) BRegion(rect));
		else {
			BRegion rectRegion(rect);
			fClippingRegion->IntersectWith(&rectRegion);
		}
	}

	return false;
}


void
DrawState::ClipToShape(shape_data* shape, bool inverse)
{
	if (shape->ptCount == 0)
		return;

	if (!fCombinedTransform.IsIdentity())
		fCombinedTransform.Apply(shape->ptList, shape->ptCount);

	BReference<AlphaMask> const mask(ShapeAlphaMask::Create(GetAlphaMask(), *shape,
		BPoint(0, 0), inverse), true);

	SetAlphaMask(mask);
}


void
DrawState::SetAlphaMask(AlphaMask* mask)
{
	// NOTE: In BeOS, it wasn't possible to clip to a BPicture and keep
	// regular custom clipping to a BRegion at the same time.
	fAlphaMask.SetTo(mask);
}


AlphaMask*
DrawState::GetAlphaMask() const
{
	return fAlphaMask.Get();
}


// #pragma mark -


void
DrawState::Transform(SimpleTransform& transform) const
{
	transform.AddOffset(fCombinedOrigin.x, fCombinedOrigin.y);
	transform.SetScale(fCombinedScale);
}


void
DrawState::InverseTransform(SimpleTransform& transform) const
{
	transform.AddOffset(-fCombinedOrigin.x, -fCombinedOrigin.y);
	if (fCombinedScale != 0.0)
		transform.SetScale(1.0 / fCombinedScale);
}


// #pragma mark -


void
DrawState::SetHighColor(rgb_color color)
{
	fHighColor = color;
}


void
DrawState::SetLowColor(rgb_color color)
{
	fLowColor = color;
}


void
DrawState::SetHighUIColor(color_which which, float tint)
{
	fWhichHighColor = which;
	fWhichHighColorTint = tint;
}


color_which
DrawState::HighUIColor(float* tint) const
{
	if (tint != NULL)
		*tint = fWhichHighColorTint;

	return fWhichHighColor;
}


void
DrawState::SetLowUIColor(color_which which, float tint)
{
	fWhichLowColor = which;
	fWhichLowColorTint = tint;
}


color_which
DrawState::LowUIColor(float* tint) const
{
	if (tint != NULL)
		*tint = fWhichLowColorTint;

	return fWhichLowColor;
}


void
DrawState::SetPattern(const Pattern& pattern)
{
	fPattern = pattern;
}


bool
DrawState::SetDrawingMode(drawing_mode mode)
{
	if (!fDrawingModeLocked) {
		fDrawingMode = mode;
		return true;
	}
	return false;
}


bool
DrawState::SetBlendingMode(source_alpha srcMode, alpha_function fncMode)
{
	if (!fDrawingModeLocked) {
		fAlphaSrcMode = srcMode;
		fAlphaFncMode = fncMode;
		return true;
	}
	return false;
}


void
DrawState::SetDrawingModeLocked(bool locked)
{
	fDrawingModeLocked = locked;
}



void
DrawState::SetPenLocation(BPoint location)
{
	fPenLocation = location;
}


BPoint
DrawState::PenLocation() const
{
	return fPenLocation;
}


void
DrawState::SetPenSize(float size)
{
	fPenSize = size;
}


//! returns the scaled pen size
float
DrawState::PenSize() const
{
	float penSize = fPenSize * fCombinedScale;
	// NOTE: As documented in the BeBook,
	// pen size is never smaller than 1.0.
	// This is supposed to be the smallest
	// possible device size.
	if (penSize < 1.0)
		penSize = 1.0;
	return penSize;
}


//! returns the unscaled pen size
float
DrawState::UnscaledPenSize() const
{
	// NOTE: As documented in the BeBook,
	// pen size is never smaller than 1.0.
	// This is supposed to be the smallest
	// possible device size.
	return max_c(fPenSize, 1.0);
}


//! sets the font to be already scaled by fScale
void
DrawState::SetFont(const ServerFont& font, uint32 flags)
{
	if (flags == B_FONT_ALL) {
		fFont = font;
		fUnscaledFontSize = font.Size();
		fFont.SetSize(fUnscaledFontSize * fCombinedScale);
	} else {
		// family & style
		if ((flags & B_FONT_FAMILY_AND_STYLE) != 0)
			fFont.SetFamilyAndStyle(font.GetFamilyAndStyle());
		// size
		if ((flags & B_FONT_SIZE) != 0) {
			fUnscaledFontSize = font.Size();
			fFont.SetSize(fUnscaledFontSize * fCombinedScale);
		}
		// shear
		if ((flags & B_FONT_SHEAR) != 0)
			fFont.SetShear(font.Shear());
		// rotation
		if ((flags & B_FONT_ROTATION) != 0)
			fFont.SetRotation(font.Rotation());
		// spacing
		if ((flags & B_FONT_SPACING) != 0)
			fFont.SetSpacing(font.Spacing());
		// encoding
		if ((flags & B_FONT_ENCODING) != 0)
			fFont.SetEncoding(font.Encoding());
		// face
		if ((flags & B_FONT_FACE) != 0)
			fFont.SetFace(font.Face());
		// flags
		if ((flags & B_FONT_FLAGS) != 0)
			fFont.SetFlags(font.Flags());
	}
}


void
DrawState::SetForceFontAliasing(bool aliasing)
{
	fFontAliasing = aliasing;
}


void
DrawState::SetSubPixelPrecise(bool precise)
{
	fSubPixelPrecise = precise;
}


void
DrawState::SetLineCapMode(cap_mode mode)
{
	fLineCapMode = mode;
}


void
DrawState::SetLineJoinMode(join_mode mode)
{
	fLineJoinMode = mode;
}


void
DrawState::SetMiterLimit(float limit)
{
	fMiterLimit = limit;
}


void
DrawState::SetFillRule(int32 fillRule)
{
	fFillRule = fillRule;
}


void
DrawState::PrintToStream() const
{
	printf("\t Origin: (%.1f, %.1f)\n", fOrigin.x, fOrigin.y);
	printf("\t Scale: %.2f\n", fScale);
	printf("\t Transform: %.2f, %.2f, %.2f, %.2f, %.2f, %.2f\n",
		fTransform.sx, fTransform.shy, fTransform.shx,
		fTransform.sy, fTransform.tx, fTransform.ty);

	printf("\t Pen Location and Size: (%.1f, %.1f) - %.2f (%.2f)\n",
		   fPenLocation.x, fPenLocation.y, PenSize(), fPenSize);

	printf("\t HighColor: r=%d g=%d b=%d a=%d\n",
		fHighColor.red, fHighColor.green, fHighColor.blue, fHighColor.alpha);
	printf("\t LowColor: r=%d g=%d b=%d a=%d\n",
		fLowColor.red, fLowColor.green, fLowColor.blue, fLowColor.alpha);
	printf("\t WhichHighColor: %i\n", fWhichHighColor);
	printf("\t WhichLowColor: %i\n", fWhichLowColor);
	printf("\t WhichHighColorTint: %.3f\n", fWhichHighColorTint);
	printf("\t WhichLowColorTint: %.3f\n", fWhichLowColorTint);
	printf("\t Pattern: %" B_PRIu64 "\n", fPattern.GetInt64());

	printf("\t DrawMode: %" B_PRIu32 "\n", (uint32)fDrawingMode);
	printf("\t AlphaSrcMode: %" B_PRId32 "\t AlphaFncMode: %" B_PRId32 "\n",
		   (int32)fAlphaSrcMode, (int32)fAlphaFncMode);

	printf("\t LineCap: %d\t LineJoin: %d\t MiterLimit: %.2f\n",
		   (int16)fLineCapMode, (int16)fLineJoinMode, fMiterLimit);

	if (fClippingRegion.IsSet())
		fClippingRegion->PrintToStream();

	printf("\t ===== Font Data =====\n");
	printf("\t Style: CURRENTLY NOT SET\n"); // ???
	printf("\t Size: %.1f (%.1f)\n", fFont.Size(), fUnscaledFontSize);
	printf("\t Shear: %.2f\n", fFont.Shear());
	printf("\t Rotation: %.2f\n", fFont.Rotation());
	printf("\t Spacing: %" B_PRId32 "\n", fFont.Spacing());
	printf("\t Encoding: %" B_PRId32 "\n", fFont.Encoding());
	printf("\t Face: %d\n", fFont.Face());
	printf("\t Flags: %" B_PRIu32 "\n", fFont.Flags());
}